#pragma once

#include <cstddef>
#include <cmath>
#include <numeric>
// #include <execution>
#include <functional>
#include <iterator>

#include <boost/circular_buffer.hpp>

#include "../utils/misc.h"

namespace manano::impl
{
    namespace detail
    {
        template <typename T>
        class dist_common
        {
        public:
            size_t nvar, bsize, n{};
            boost::circular_buffer<T> buf, dist;

        protected:
            template <typename It1, typename It2>
            void push_new(It1 begin, It2 end)
            {
                if (dist.full())
                {
                    bsize *= 2;
                    dist.set_capacity(bsize);
                    buf.set_capacity(bsize * nvar);
                }
                while (begin != end)
                {
                    buf.push_back(*begin++);
                }
                dist.push_back(0);
                n += 1;
            }

        public:
            explicit dist_common(size_t nvar, size_t nbuf0 = 16)
                : nvar(nvar), bsize(nbuf0),
                  buf(nvar * nbuf0), dist(nbuf0)
            {
            }

            template <typename... Args>
            void remove_impl(Args...)
            {
                buf.erase_begin(nvar);
                dist.pop_front();
                n -= 1;
            }

            void reset_impl()
            {
                buf.clear();
                dist.clear();
                n = 0;
            }
        };
    } // namespace detail

    template <typename T>
    class euclidean_dist : public detail::dist_common<T>
    {
        using base = dist_common<T>;

    public:
        using base::base;

        template <typename Iter1, typename Iter2>
        void insert_impl(Iter1 begin, Iter2 end)
        {
            assert(std::distance(begin, end) == this->nvar);
            auto base_it = this->buf.begin();
            for (size_t i = 0; i < this->n; ++i)
            {
                this->dist[i] = std::sqrt(std::transform_reduce(begin, end,
                                                                base_it,
                                                                T{},
                                                                std::plus<T>{},
                                                                [](const auto &x, const auto &y)
                                                                {
                                                                    auto d = x - y;
                                                                    return d * d;
                                                                }));
                base_it += this->nvar;
            }
            this->push_new(begin, end);
        }
    };

    template <typename T>
    class manhattan_dist : public detail::dist_common<T>
    {
        using base = dist_common<T>;

    public:
        using base::base;

        template <typename Iter1, typename Iter2>
        void insert_impl(Iter1 begin, Iter2 end)
        {
            assert(std::distance(begin, end) == this->nvar);
            auto base_it = this->buf.begin();
            for (size_t i = 0; i < this->n; ++i)
            {
                this->dist[i] = std::transform_reduce(begin, end,
                                                      base_it,
                                                      T{},
                                                      std::plus<T>{},
                                                      [](const auto &x, const auto &y)
                                                      {
                                                          return std::abs(x - y);
                                                      });
                base_it += this->nvar;
            }
            this->push_new(begin, end);
        }
    };
}
